rusty-copybook
=================

COBOL copybooks to Rust transpiler

## Status 🚧
Under development.

## Feaatures
This crate has no ambition to cover whole COBOL specification, new feaatures will be addedd as needed. Supported features include
* COBOL copybooks recursive descent parser
* Packed decimals
* Leading/trailing/separate signs
* REDEFINES clause

Missing
* Setters
* EBCDIC convertor
* OCCURS clause

## Usage
This crate is intended to be use as both library and standalone application. For exmaple it is possible to include rusty-copybook::codegen invocation into your build.rs to generate rust structs at build time and reference the library in Cargo.toml to actually use the code.

### Exmaple
Given a COBOL copybook from
```cobol
000001 **********************************************
000002 * SAMPLE COPYBOOK
000003 ***********************************************
000004 01 :PRE:-USER.
000005  02 :PRE:-NAME.
000006   03 :PRE:FIRST                   PIC X(10).
000007   03 :PRE:-LAST                   PIC X(10).
000009  02 :PRE:-BAL                     PIC S9(10)V9(2).
000010  02 :PRE:-BAD REDEFINES :PRE:-BAL PIC 9(12).
```

run `rusty-copybook --copybook sample.cpy --output sample.rs --drop-field-prefix :PRE:` to produce Rust module like this:

```rust
#[allow(unused_imports)]
use rust_decimal::Decimal;
use rusty_copybook::{Field, Spec, DecimalSpec, StringSpec, DecimalFormat, Sign};
use rusty_copybook::{Error, Result};

const RECORD_LEN: usize = 44;

#[allow(dead_code)]
pub struct Sample {
    bytes: [u8; RECORD_LEN],
    first: Field,
    last: Field,
    bal: Field,
    bad: Field,
    }

impl Sample {
    pub fn new(input: &[u8]) -> Result<Sample>{
        if input.len() > RECORD_LEN {
            return Err(Error::BufferTooShort {
                expected: RECORD_LEN,
                actual: input.len()
            })
        }
        let mut bytes: [u8; RECORD_LEN] = [0; RECORD_LEN];
        bytes[..input.len()].copy_from_slice(input);
        
        let first = Field::new(3, "FIRST", 0,
                          Spec::String(StringSpec{
                              length: 10,
                              value: None
                          }));

        
        let last = Field::new(3, "LAST", 10,
                          Spec::String(StringSpec{
                              length: 10,
                              value: None
                          }));

        
        let bal = Field::new(2, "BAL", 20,
                          Spec::Decimal(DecimalSpec{
                              scale: Some(2),
                              precision: 10,
                              sign: Some(Sign {
                                  leading: false,
                                  separate: false
                              }),
                              format: DecimalFormat::None,
                              value: None
                          }));

        
        let bad = Field::new(2, "BAD", 20,
                          Spec::Decimal(DecimalSpec{
                              scale: None,
                              precision: 12,
                              sign: None,
                              format: DecimalFormat::None,
                              value: None
                          }));

        
        Ok(Sample{
            bytes,
            first,
            last,
            bal,
            bad,
            })
    }
    pub fn as_bytes(&self) -> &[u8]{
        &self.bytes
    }
    pub fn length(&self) -> u32 {
        RECORD_LEN as u32
    }

    pub fn get_first(&self) -> Result<String> {
        self.first.get_string(&self.bytes) 
    }

    pub fn get_last(&self) -> Result<String> {
        self.last.get_string(&self.bytes) 
    }

    pub fn get_bal(&self) -> Result<Decimal> {
        self.bal.get_decimal(&self.bytes) 
    }

    pub fn get_bad(&self) -> Result<Decimal> {
        self.bad.get_decimal(&self.bytes) 
    }

        
}
```
